package com.tsystems.services;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import com.tsystems.dao.StationDAO;
import com.tsystems.dao.TimeTableElementDAO;
import com.tsystems.dao.TrainDAO;
import com.tsystems.entities.Station;
import com.tsystems.entities.TimeTableElement;
import com.tsystems.entities.Train;

/**
 * 
 * @author 4194
 * 
 *         Класс, отвечающий за операции с элементами расписания.
 */
public class TimeTableElementServices {

	private static TimeTableElementServices instance;

	private TimeTableElementServices() {

	}

	public static synchronized TimeTableElementServices getInstance() {
		if (instance == null) {
			instance = new TimeTableElementServices();
		}
		return instance;
	}

	/**
	 * Добавляет в базу данных элемент расписания.
	 * 
	 * @param timeTableElement
	 *            - элемент расписания который необходимо добавить.
	 * @return "Элемент расписания успешно добавлен" если все прошло нормально,
	 *         иначе - сообщение об ошибке.
	 */
	public synchronized String addTimeTableElement(
			TimeTableElement timeTableElement) {

		TrainDAO trainDAO = new TrainDAO();
		Train trainFound = trainDAO.getTrainByNumber(timeTableElement
				.getTrain().getNumber());
		if (trainFound == null) {
			return "Поезд не найден.";
		}

		StationDAO stationDAO = new StationDAO();
		Station stationFound = stationDAO.getStationByName(timeTableElement
				.getArrivalStation().getName());

		if (stationFound == null) {
			return "Станция прибытия не найдена.";
		}
		stationFound = null;
		stationFound = stationDAO.getStationByName(timeTableElement
				.getDepartureStation().getName());

		if (stationFound == null) {
			return "Станция отправления не найдена";
		}
		if (timeTableElement.getArrivalTime().compareTo(
				timeTableElement.getDepartureTime()) < 0) {
			return "Время отправления позже времени прибытия!";
		}

		TimeTableElementDAO timeTableElementDAO = new TimeTableElementDAO();
		TimeTableElement timeTableElementFound = timeTableElementDAO
				.getEqualsTimeTableElement(timeTableElement);

		if (timeTableElementFound != null) {
			return "Такой элемент расписания уже существует в базе!";
		}

		timeTableElementDAO.insert(timeTableElement);
		return "Элемент расписания успешно добавлен";
	}

	/**
	 * Возвращает список элементов расписания содержащихся в базе.
	 * @return список элементов расписания.
	 */
	public synchronized List<TimeTableElement> viewTimeTableElementsBase() {

		TimeTableElementDAO timeTableElementDAO = new TimeTableElementDAO();
		List<TimeTableElement> timeTableElements = timeTableElementDAO
				.getTimeTableElementBase();
		return timeTableElements;

	}

	/**
	 * Находит все поезда и пути от станции A до станции B, в промежутке времени от timeA до timeB
	 * @param stationA - станция А
	 * @param stationB - станция B
	 * @param timeA - время не раньше которого поезд должен прибыть на станцию A
	 * @param timeB - время не позже которого поезд должен прибыть на станцию B
	 * @return HashMap <Поезд, список, описывающий маршрут по которому поезд может добраться из станции A в станцию B>
	 */
	public synchronized HashMap<Train, List<TimeTableElement>> findTrain(
			Station stationA, Station stationB, Date timeA, Date timeB) {
		TimeTableElementDAO timeTableElementDAO = new TimeTableElementDAO();
		List<TimeTableElement> departureList = timeTableElementDAO
				.getTimeTableElementsByDepartureStation(stationA);
		List<TimeTableElement> arrivalList = timeTableElementDAO
				.getTimeTableElementsByArrivalStation(stationB);

		Iterator<TimeTableElement> departureIter = departureList.iterator();
		while (departureIter.hasNext()) {
			TimeTableElement departureTTE = departureIter.next();
			if (departureTTE.getDepartureTime().compareTo(timeA) < 0) {
				departureIter.remove();
			}
		}

		Iterator<TimeTableElement> arrivalIter = arrivalList.iterator();
		while (arrivalIter.hasNext()) {
			TimeTableElement arrivalTTE = arrivalIter.next();
			if (arrivalTTE.getArrivalTime().compareTo(timeB) > 0) {
				arrivalIter.remove();
			}
		}
		if (departureList.isEmpty() || arrivalList.isEmpty()) {
			return new HashMap<Train, List<TimeTableElement>>();
		}
		HashMap<Train, List<TimeTableElement>> resultList = new HashMap<Train, List<TimeTableElement>>();

		for (TimeTableElement arrivalElement : arrivalList) {
			for (TimeTableElement departureElement : departureList) {
				if (arrivalElement.getTrain().equals(
						departureElement.getTrain())) {
					List<TimeTableElement> tteList = mergePath(
							departureElement, arrivalElement);
					if (tteList.isEmpty()) {
						continue;
					}
					resultList.put(arrivalElement.getTrain(), tteList);
				}
			}
		}

		return resultList;
	}

	/**
	 * Метод пытается построить маршрут между 2 элементами расписания, путем
	 * "слияния" элементов расписания у которых совпадают время и станция
	 * прибытия со временем и станцией отправления.
	 * 
	 * @param startElement
	 *            - стартовый элемент расписания.
	 * @param finishElement
	 *            - конечный элемент расписания
	 * @return пустой список, если маршрут некорректен, иначе список содержащий
	 *         маршрут.
	 */
	public synchronized List<TimeTableElement> mergePath(
			TimeTableElement startElement, TimeTableElement finishElement) {

		
		if (!startElement.getTrain().equals(finishElement.getTrain())) {
			return Collections.emptyList();
		}
		ArrayList<TimeTableElement> result = new ArrayList<TimeTableElement>(20);
		result.add(startElement);
		if (startElement.equals(finishElement)) {
			return result;
		}
		TimeTableElementDAO timeTableElementDAO = new TimeTableElementDAO();
		if(timeTableElementDAO.getEqualsTimeTableElement(startElement)==null){
			return Collections.emptyList();
		}
		if(timeTableElementDAO.getEqualsTimeTableElement(finishElement)==null){
			return Collections.emptyList();
		}
		List<TimeTableElement> pathParts = timeTableElementDAO
				.getTimeTableElementsByTrain(startElement.getTrain());
		TimeTableElement currentElement = startElement;
		Iterator<TimeTableElement> iterTTE = pathParts.iterator();
		TimeTableElement tempElem;
		while (!pathParts.isEmpty()) {
			iterTTE = pathParts.iterator();
			tempElem = null;
			while (iterTTE.hasNext()) {
				TimeTableElement element = iterTTE.next();
				if (startElement.equals(element)) {
					iterTTE.remove();
					continue;
				}
				if (currentElement.getArrivalStation().equals(
						element.getDepartureStation())
						&& currentElement.getArrivalTime().equals(
								element.getDepartureTime())) {
					if (tempElem != null) {
						// Эта ситуация происходит в случае "развилки", когда мы
						// из одной станции, пытаемся уехать в разных
						// направлениях в одно и то же время
						return Collections.emptyList();
					}
					tempElem = element;
					result.add(tempElem);
					iterTTE.remove();

				}
			}
			if (finishElement.equals(tempElem)) {
				return result;
			}
			if (tempElem != null) {
				currentElement = tempElem;
			}else{
				return Collections.emptyList();
			}
			
			

		}

		return Collections.emptyList();

	}

	/**
	 * Возвращает список элементов расписания по определенной станции отправления.
	 * @param station - станция отправления
	 * @return список элементов расписания.
	 */
	public synchronized List<TimeTableElement> getTimeTable(Station station) {

		TimeTableElementDAO timeTableElementDAO = new TimeTableElementDAO();
		List<TimeTableElement> departureList = timeTableElementDAO
				.getTimeTableElementsByDepartureStation(station);

		return departureList;
	}

	/**
	 * Возвращает список элементов расписания по определенной станции прибытия.
	 * @param station - станция прибытия.
	 * @return список элементов расписания.
	 */
	public synchronized List<TimeTableElement> getArrivalTimeTable(
			Station station) {

		TimeTableElementDAO timeTableElementDAO = new TimeTableElementDAO();
		List<TimeTableElement> arrivalList = timeTableElementDAO
				.getTimeTableElementsByArrivalStation(station);

		return arrivalList;
	}

}
